﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Syspetro.Core.Extensions
{
    /// <summary>
    /// 不等于,大于,大于等于,小于,小于等于,等于
    /// 模糊匹配(like),包含(in)
    /// </summary>
    public class LinqOption
    {
        public string Type { get; set; }
        public string Key { get; set; }
        public string Value { get; set; }
    }
    public static class LinqHelper
    {
        /// <summary>
        /// 获取指定字段类型
        /// </summary>
        /// <param name="t"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static Type GetParameter(Type t, string propertyName)
        {
            #region 表达式目录树方式
            if (string.IsNullOrEmpty(propertyName))
                return t;
            Expression propertySelector = Expression.Parameter(t, "p");//创建参数p
            var listsp = propertyName.Split('.');
            foreach (var sp in listsp)
            {
                propertySelector = Expression.Property(propertySelector, sp);
                t = propertySelector.Type;
            }
            return t;
            #endregion

            #region 反射方式
            //if (propertyName.Contains('.'))
            //{
            //    int index = propertyName.IndexOf('.');
            //    string sp1 = propertyName.Substring(0, index);
            //    string sp2 = propertyName.Substring(index + 1, propertyName.Length - index - 1);
            //    var ps = t.GetProperties();
            //    foreach (var p in ps)
            //    {
            //        if (string.Compare(p.Name, sp1, true) == 0)
            //        {
            //            if (string.IsNullOrEmpty(sp2))
            //                return p.PropertyType;
            //            else
            //                return GetParameter(p.PropertyType, sp2);
            //        }
            //    }
            //}
            //else
            //{
            //    var ps = t.GetProperties();
            //    foreach (var p in ps)
            //    {
            //        if (string.Compare(p.Name, propertyName, true) == 0)
            //        {
            //            return p.PropertyType;
            //        }
            //    }
            //}
            //return t;
            #endregion
        }
        /// <summary>
        /// 字符串转换成Linq
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_options"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateExpression<T>(List<LinqOption> _options, ExpressionType expression = ExpressionType.AndAlso) where T : new()
        {
            if (_options == null)
            {
                return null;
            }
            var options = _options.Where(t => !string.IsNullOrEmpty(t.Value.Trim())).ToList();
            if (options == null || options.Count == 0)
            {
                return null;
            }
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            var ex = CreateExpression<T>(options[0].Key, options[0].Type, options[0].Value, newParameter);
            for (int i = 1; i < options.Count; i++)
            {
                var ex2 = CreateExpression<T>(options[i].Key, options[i].Type, options[i].Value, newParameter);
                if (expression == ExpressionType.OrElse)
                    ex = OrElse<T>(ex, ex2, newParameter);
                else if (expression == ExpressionType.AndAlso)
                    ex = AndAlso<T>(ex, ex2, newParameter);
                else
                    throw new Exception("不支持该方法");
            }
            return Expression.Lambda<Func<T, bool>>(ex, newParameter);
        }
        /// <summary>
        /// 创建lambda表达式：p=>p.propertyName
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static Expression<Func<T, object>> CreateExpression<T>(string propertyName)
        {
            var parameter = Expression.Parameter(typeof(T), "p");//创建参数p
            Expression propertySelector = parameter;
            var listsp = propertyName.Split('.');
            foreach (var sp in listsp)
            {
                propertySelector = Expression.Property(propertySelector, sp);
            }
            propertySelector = Expression.Convert(propertySelector, typeof(object));
            return Expression.Lambda<Func<T, object>>(propertySelector, parameter);
        }
        /// <summary>
        /// 创建lambda表达式：p=>p.propertyName
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TType"></typeparam>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static Expression<Func<T, TType>> CreateExpression<T, TType>(string propertyName)
        {
            var parameter = Expression.Parameter(typeof(T), "p");//创建参数p
            Expression propertySelector = parameter;
            var listsp = propertyName.Split('.');

            Type pt = typeof(TType);
            foreach (var sp in listsp)
            {
                propertySelector = Expression.Property(propertySelector, sp);
                pt = propertySelector.Type;
            }
            if (pt == typeof(TType))
                return Expression.Lambda<Func<T, TType>>(propertySelector, parameter);
            else
                throw new Exception("类型错误!");
        }
        /// <summary>
        /// 创建lambda表达式：p=>p.propertyName == propertyValue
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <param name="type"></param>
        /// <param name="propertyValue"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateExpression<T>(string propertyName, string type, string propertyValue)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), "p");//创建参数p
            return Expression.Lambda<Func<T, bool>>(ToLinq<T>(propertyName, type, propertyValue, parameter), parameter);
        }

        private static Expression CreateExpression<T>(string propertyName, string type, string propertyValue, ParameterExpression parameter = null)
        {
            if (type == "in")
            {
                return ParaseIn<T>(parameter, propertyName.Trim(), propertyValue.Trim());
            }
            else
            {
                return ToLinq<T>(propertyName.Trim(), type, propertyValue.Trim(), parameter);
            }
        }
        private static Expression ToLinq(string type, MemberExpression pe, ConstantExpression value)
        {
            return type switch
            {
                "!=" => Expression.NotEqual(pe, value),
                ">" => Expression.GreaterThan(pe, value),
                ">=" => Expression.GreaterThanOrEqual(pe, value),
                "<" => Expression.LessThan(pe, value),
                "<=" => Expression.LessThanOrEqual(pe, value),
                "=" => Expression.Equal(pe, value),
                "like" => ToLinqLike(pe, value),
                _ => throw new Exception("不支持该方法"),
            };
        }
        private static Expression ParaseIn<T>(ParameterExpression parameter, string propertyName, string propertyValue, bool isEqual = true)
        {
            var valueArr = propertyValue.Split(',');
            Expression expression1;

            if (parameter == null)
                parameter = Expression.Parameter(typeof(T), "p");//创建参数p
            var keyMember = (MemberExpression)GetPropertySelector(parameter, propertyName);
            Type keyType = keyMember.Type;
            ConstantExpression constant = Expression.Constant(TypeHelper.ChangeTo(keyType, valueArr[0]), keyType);//创建常数
            if (isEqual)
                expression1 = ToLinq("=", keyMember, constant);
            else
                expression1 = ToLinq("like", keyMember, constant);

            for (int i = 1; i < valueArr.Length; i++)
            {
                keyType = keyMember.Type;
                Expression expression2;
                constant = Expression.Constant(TypeHelper.ChangeTo(keyType, valueArr[i]), keyType);//创建常数
                if (isEqual)
                    expression2 = ToLinq("=", keyMember, constant);
                else
                    expression2 = ToLinq("like", keyMember, constant);
                expression1 = OrElse<T>(expression1, expression2, parameter);
            }
            return expression1;
        }
        private static Expression ToLinqLike(MemberExpression pe, ConstantExpression constant)
        {
            MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            return Expression.Call(pe, method, constant);
        }
        private static Expression ToLinq<T>(string propertyName, string type, string propertyValue, ParameterExpression parameter = null)
        {
            if (parameter == null)
                parameter = Expression.Parameter(typeof(T), "p");//创建参数p
            var member = GetPropertySelector(parameter, propertyName);
            var tt = member.Type;
            ConstantExpression constant = Expression.Constant(TypeHelper.ChangeTo(tt, propertyValue), tt);//创建常数
            return ToLinq(type, (MemberExpression)member, constant);
        }
        private static Expression GetPropertySelector(ParameterExpression parameter, string propertyName)
        {
            Expression propertySelector = parameter;
            var listsp = propertyName.Split('.');
            foreach (var sp in listsp)
            {
                propertySelector = Expression.Property(propertySelector, sp);
            }
            return propertySelector;
        }
        /// <summary>
        /// 合并表达式 expr1 AND expr2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <param name="newParameter"></param>
        /// <returns></returns>
        private static Expression AndAlso<T>(Expression expr1, Expression expr2, ParameterExpression newParameter)
        {
            if (expr1 == null)
                return expr2;
            else if (expr2 == null)
                return expr1;
            MyExpressionVisitor visitor = new(newParameter);
            var left = visitor.Visit(expr1);
            var right = visitor.Visit(expr2);
            return Expression.AndAlso(left, right);
        }
        /// <summary>
        /// 合并表达式 expr1 or expr2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <param name="newParameter"></param>
        /// <returns></returns>
        private static Expression OrElse<T>(Expression expr1, Expression expr2, ParameterExpression newParameter)
        {
            if (expr1 == null)
                return expr2;
            else if (expr2 == null)
                return expr1;
            MyExpressionVisitor visitor = new(newParameter);

            var left = visitor.Visit(expr1);
            var right = visitor.Visit(expr2);
            return Expression.OrElse(left, right);
        }
    }
    public class MyExpressionVisitor : ExpressionVisitor
    {
        public ParameterExpression Parameter { get; set; }
        public MyExpressionVisitor() { }
        public MyExpressionVisitor(ParameterExpression parameter)
        {
            this.Parameter = parameter;
        }
        public override Expression Visit(Expression node)
        {
            return base.Visit(node);
        }

        protected override Expression VisitParameter(ParameterExpression parameter)
        {
            return this.Parameter;
        }
    }
}
